﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace RconLibrary.Protocol
{
    /// <summary>
    /// Packet class.
    /// </summary>
    public class Packet
    {
        /// <summary>
        /// Gets the sequence number.
        /// </summary>
        public int Sequence
        {
            get;
            internal set;
        }

        /// <summary>
        /// Gets the final size of the packet.
        /// </summary>
        public int Size
        {
            get
            {
                int size = 12;
                foreach (var word in this.wordlist)
                {
                    size += word.Length + 5; // string size + size + trailing byte
                }
                return size;
            }
        }

        /// <summary>
        /// Gets the packet origin.
        /// </summary>
        public PacketOrigin Origin
        {
            get;
            internal set;
        }

        /// <summary>
        /// Gets the packet type.
        /// </summary>
        public PacketType Type
        {
            get;
            internal set;
        }

        /// <summary>
        /// Gets the packet's words.
        /// </summary>
        public string[] Words
        {
            get
            {
                return this.wordlist.ToArray();
            }
        }

        /// <summary>
        /// Gets the packet's word list.
        /// </summary>
        internal List<string> WordList
        {
            get
            {
                return this.wordlist;
            }
            set
            {
                if (value == null)
                {
                    this.wordlist = new List<string>();
                    return;
                }
                this.wordlist = value;
            }
        }

        /// <summary>
        /// Stores the word list.
        /// </summary>
        private List<string> wordlist;

        /// <summary>
        /// Creates a packet.
        /// </summary>
        /// <param name="sequence">The packet sequence number.</param>
        /// <param name="origin">The packet origin.</param>
        /// <param name="type">The packet type.</param>
        public Packet(int sequence, PacketOrigin origin, PacketType type)
        {
            this.Sequence = sequence;
            this.Origin = origin;
            this.Type = type;
            this.wordlist = new List<string>();
        }

        /// <summary>
        /// Creates a packet.
        /// </summary>
        /// <param name="sequence">The packet sequence number.</param>
        /// <param name="origin">The packet origin.</param>
        /// <param name="type">The packet type.</param>
        /// <param name="parameters">The list of packet parameters.</param>
        public Packet(int sequence, PacketOrigin origin, PacketType type, IEnumerable<object> parameters)
        {
            this.Sequence = sequence;
            this.Origin = origin;
            this.Type = type;
            this.wordlist = this.ConvertParameters(new List<object>(parameters));
        }

        /// <summary>
        /// Encodes the packet to a network-ready byte array.
        /// </summary>
        /// <returns>Byte array containing the packet data.</returns>
        public byte[] Encode()
        {
            DataHelper converter = new DataHelper();
            converter.IsLittleEndian = true;

            byte[] headBytes = converter.GetBytes((uint)(
                (uint)(this.Sequence & 0x3FFFFFFF) |
                (uint)((this.Origin == PacketOrigin.Client) ? 0x80000000 : 0) |
                (uint)((this.Type == PacketType.Response) ? 0x40000000 : 0)
            ));

            List<byte> wordBytes = new List<byte>();
            foreach (var word in this.wordlist)
            {
                byte[] wordHead = converter.GetBytes(word.Length);
                wordBytes.AddRange(wordHead); // size

                byte[] wordBuffer = Encoding.ASCII.GetBytes(word); // TODO: Check if there's any null bytes (throw an exception)
                wordBytes.AddRange(wordBuffer);

                wordBytes.Add(0); // trailing null byte
            }

            List<byte> bytes = new List<byte>();
            bytes.AddRange(headBytes);
            bytes.AddRange(converter.GetBytes(wordBytes.Count + 12));
            bytes.AddRange(converter.GetBytes(this.wordlist.Count));
            bytes.AddRange(wordBytes);

            return bytes.ToArray();
        }

        /// <summary>
        /// Converts the parameters to a wordlist.
        /// </summary>
        /// <returns>List of string parameters.</returns>
        private List<string> ConvertParameters(IEnumerable<object> parameters)
        {
            List<string> wordlist = new List<string>();
            List<object> paramlist = new List<object>(parameters);

            foreach (var param in paramlist)
            {
                if (param.IsString())
                {
                    wordlist.Add((string)param);
                }
                else if (param.IsBoolean())
                {
                    wordlist.Add((bool)param ? "true" : "false");
                }
                else if (param.IsNumber())
                {
                    wordlist.Add(param.ToString());
                }
                else if (param is IComposite)
                {
                    var composite = param as IComposite;
                    wordlist.AddRange(this.ConvertParameters(composite.GetParameters()));
                }
            }

            return wordlist;
        }

    }

}
